{
 DM： 数据模式窗口，放置一些不可见控件，或全局使用的函数
}

unit unit_dm;

{$mode ObjFPC}{$H+}

interface

uses
  Classes, SysUtils, Graphics, Dialogs, Windows, DateUtils,
  base64,
  FileUtil, Forms, Controls, ExtCtrls, IniPropStorage, IdUDPServer, IdUDPClient,
  sockets, IdSocketHandle, IdGlobal;

type

  { TDM }

  TDM = class(TDataModule)
    IdUDPClient_setup: TIdUDPClient;
    IdUDPClient_sent: TIdUDPClient;
    IdUDPServer_setup: TIdUDPServer;
    IniPropStorage1: TIniPropStorage;
    Timer_debug: TTimer;
    Timer_hand: TTimer;
    Timer_link: TTimer;
    procedure DataModuleCreate(Sender: TObject);
    procedure IdUDPServer_setupUDPRead(AThread: TIdUDPListenerThread;
      const AData: TIdBytes; ABinding: TIdSocketHandle);
    procedure Timer_debugTimer(Sender: TObject);
    procedure Timer_handTimer(Sender: TObject);
    procedure Timer_linkTimer(Sender: TObject);
  private
    I_REC_SERVER_PORT: integer;
    I_REC_SERVER_BUFF: integer;
    B_REGISTERED_ERR: boolean;          // 连接服务器失败
  public
    S_user_name, S_user_dept: string;   // 注册用户名和单位
    S_version: string;                  // 程序版本号，从about页面取得

    // 截屏时，向左、上、下、右偏移数值（有多个屏幕时使用）
    g_left, g_up, g_down, g_rigth: integer;

    // 发送的参数，如果改变，服务端提醒待办
    S_param, S_next_param: string;

    B_param_enter: boolean;             // 客户端修改参数
    B_param_screen_tip: boolean;        // 客户端多个屏幕，提醒一次否

    S_MESSAGE: string;                  // 显示的信息

    S_SYS_SERVER_IP: string;            // 系统服务器IP地址
    S_REC_SERVER_IP: string;            // 接收图像服务器IP地址
    I_REC_SERVER_NUM: integer;

    W_y, W_m, W_d: word;                // 年 月 日 时 分
    W_h, W_f: word;

    function Open_UDPClient(): boolean;
    function IsHomology(bmp1, bmp2: Graphics.TbitMap; I: integer = 0): boolean;
    procedure IsToday;                  // 是否今天
  end;

var
  DM: TDM;

implementation

uses Unit_main, Unit_setup, Unit_other;

  {$R *.lfm}

  { TDM }

procedure TDM.DataModuleCreate(Sender: TObject);
begin
  // 启动时的初始化，停止定时器，停止所有服务
  Timer_link.Enabled := False;
  Timer_hand.Enabled := False;
  Timer_debug.Enabled := False;
  IdUDPServer_setup.Active := False;
  IdUDPClient_sent.Active := False;
  IdUDPClient_setup.Active := False;
  S_param := '';
  B_param_enter := False;
  B_param_screen_tip := False;

  // 初始化参数：当前路径，提示
  S_MESSAGE := '提示';

  IniPropStorage1.IniSection := '配置';
  IniPropStorage1.Active := True;
  S_SYS_SERVER_IP := IniPropStorage1.ReadString('服务器地址', '');
  IniPropStorage1.Active := False;
  S_REC_SERVER_IP := '';

  if S_SYS_SERVER_IP = '' then
  begin
    S_MESSAGE := '错误，服务器IP地址没有设置，请到设置页面修改!';
    exit;
  end;

  try
    IdUDPClient_setup.Host := S_SYS_SERVER_IP;
    IdUDPClient_setup.Port := 5100;     // 设置服务器默认端口
    IdUDPClient_setup.BufferSize := 8192;
    IdUDPClient_setup.Active := True;

    IdUDPServer_setup.DefaultPort := 5101;
    // 接收设置服务器信息，本机的默认端口
    IdUDPServer_setup.BufferSize := 8192;
    IdUDPServer_setup.Active := True;   // 接收设置服务器信息
  except
    S_MESSAGE := '错误，服务器IP地址不对，请到设置页面修改!';
    exit;
  end;
  B_REGISTERED_ERR := True;

  // 1秒定时，开始连接服务器
  Timer_link.Interval := 1000;
  Timer_link.Enabled := True;
  IsToday;                              // 保存今天的年月日参数
end;

procedure TDM.IdUDPServer_setupUDPRead(AThread: TIdUDPListenerThread;
  const AData: TIdBytes; ABinding: TIdSocketHandle);
var
  S: ansistring = '';
  S_user: string;
begin

  // 不是从系统服务器和图像服务器发过来的数据，不理会
  if (ABinding.PeerIP <> S_SYS_SERVER_IP) and (ABinding.PeerIP <>
    S_REC_SERVER_IP) then
  begin
    S_MESSAGE := '提示，收到非法设备发来的数据';
    Form_main.ShowMyMsg;
    exit;
  end;

  // 从配置服务器接收的信息
  SetLength(S, Length(AData));
  Move(Pointer(AData)^, Pointer(S)^, Length(AData));

  // 39 13 104 9 17 11 5 7 21 110 6
  if Length(AData) = 39 then
  begin
    // 格式：IPADDRESS 000000000000000 0000 000000 00000
    // 位数：   9      15 (不足前部0)    4      6    5
    // 说明：   命令    图像服务器IP   端口  缓存   编号
    if (CompareText(Copy(S, 1, 9), 'IPADDRESS') = 0) then
    begin
      S_REC_SERVER_IP := Trim(Copy(S, 10, 15));
      // 服务器IP为15位0，表示没有可用
      if (S_REC_SERVER_IP = '000000000000000') then
      begin
        S_MESSAGE := '提示，无可用接收图像服务器';
        Form_main.ShowMyMsg;

        exit;
      end;

      I_REC_SERVER_PORT := StrToIntDef(Copy(S, 25, 4), 5102);
      I_REC_SERVER_BUFF := StrToIntDef(Copy(S, 29, 6), 8192);
      I_REC_SERVER_NUM := StrToIntDef(Copy(S, 35, 5), 0) + 1;
      // 今天图像序号

      // 关5秒连接定时器，设置截屏服务器地址,开始截屏操作
      Timer_link.Enabled := False;

      S_MESSAGE := '成功连接到服务器';
      Form_main.ShowMyMsg;
      Form_main.Action_begin.Execute;   // 开始正常操作

      // 启动30秒握手检测
      Timer_hand.Enabled := True;

      Exit;
    end;
  end
  else if Length(AData) = 13 then
  begin
    // ********* 第一步：发送 Registered **********
    // 回复 1：接收询问注册，准备注册用户和单位
    if (CompareText(S, 'AskRegistered') = 0) then
    begin
      // 服务器提示要注册用户
      S_MESSAGE := '请注册用户';
      S_user_name := '(未注册用户)';
      S_user_dept := '';
      with Form_main do
      begin
        ShowMyMsg;
        StaticText1.Caption := '欢迎使用录屏采集器，请及时注册用户';
        Edit_name.ReadOnly := False;
        Edit_name.Color := clWindow;
        Edit_dept.ReadOnly := False;
        Edit_dept.Color := clWindow;
        Action_regedit.Enabled := True;
      end;

      Exit;
    end;
  end
  else if Length(AData) = 104 then
  begin
    // ********* 第一步：发送 Registered **********
    // 回复 2：接收用户和单位
    if (CompareText(Copy(S, 1, 4), 'User') = 0) then
    begin
      // 服务器回传用户信息
      S_user := DecodeStringBase64(Copy(S, 5, 100));

      S_user_name := Trim(Copy(S_user, 1, Pos('|', S_user) - 1));
      S_user_dept := Trim(Copy(S_user, Pos('|', S_user) + 1, Length(S_user)));
      with Form_main do
      begin
        if (S_user_name <> Edit_name.Text) or (S_user_dept <> Edit_dept.Text) then
        begin
          Label_url.Visible := False;
          Label_rec_tip.Visible := True; // 自动设置为服务器信息

          Edit_name.Text := DM.S_user_name;
          Edit_dept.Text := DM.S_user_dept;

          IniPropStorage1.IniSection := '注册';
          IniPropStorage1.Active := True;
          IniPropStorage1.WriteString('用户', Trim(Edit_name.Text));
          IniPropStorage1.WriteString('部门', Trim(Edit_dept.Text));
          IniPropStorage1.Active := False;
        end
        else
        begin
          Label_rec_tip.Visible := False;
        end;
        ShowMyself;
        StaticText1.Caption := '欢迎使用录屏采集器';
        Edit_name.ReadOnly := True;
        Edit_name.Color := clBtnFace;
        Edit_dept.ReadOnly := True;
        Edit_dept.Color := clBtnFace;
        Action_regedit.Enabled := False;
      end;

      Exit;
    end;
  end
  else if Length(AData) = 9 then
  begin
    // 服务器设置为：图像接收后，立即返回握手信息：Handshake
    if (CompareText(S, 'Handshake') = 0) then
    begin
      // 只有收到返回握手，才表示接收了一个图像
      I_REC_SERVER_NUM := I_REC_SERVER_NUM + 1;

      // 重新启动30秒握手检测
      Timer_hand.Enabled := False;
      Timer_hand.Enabled := True;

      Exit;
    end;
  end
  else if Length(AData) = 17 then
  begin
    // 服务器设置为：图像接收后，立即返回握手信息：Handshake
    if (CompareText(S, 'HandshakeEmptyPic') = 0) then
    begin
      // 重新启动30秒握手检测
      Timer_hand.Enabled := False;
      Timer_hand.Enabled := True;

      Exit;
    end;
  end
  else if Length(AData) = 11 then
  begin
    if (CompareText(S, 'ResetSystem') = 0) then
    begin
      // 服务器要求停止，直接去握手失败处理
      Timer_handTimer(nil);

      Exit;
    end;
  end
  else if Length(AData) = 5 then
  begin
    if (CompareText(S, 'Debug') = 0) then
    begin
      // 开启Debug模式，1分钟后自动退出
      Form_setup.TabSheet_debug.TabVisible := True;
      S_MESSAGE := '调试模式开启';
      Form_main.ShowMyMsg;
      Timer_debug.Enabled := False;
      Timer_debug.Interval := 600000;    // 10分钟后自动关闭
      Timer_debug.Enabled := True;
      // 保存
      S_next_param := S_param;

      Exit;
    end;
  end
  else if Length(AData) = 7 then
  begin
    if (CompareText(S, 'NoDebug') = 0) then
    begin
      // 关闭Debug模式，暂时未启用
      Form_setup.TabSheet_debug.TabVisible := False;
      S_MESSAGE := '调试模式关闭';
      Form_main.ShowMyMsg;
      Timer_debug.Enabled := False;

      Exit;
    end;
  end
  else if Length(AData) = 21 then
  begin
    // 服务端传来的参数
    // x-y-w-h
    if (CompareText(Copy(S, 1, 5), 'SETUP') = 0) then
    begin
      g_left := StrToIntDef(Copy(S, 6, 4), 0);
      g_up := StrToIntDef(Copy(S, 10, 4), 0);
      g_down := StrToIntDef(Copy(S, 14, 4), 0);
      g_rigth := StrToIntDef(Copy(S, 18, 4), 0);

      S_MESSAGE := '参数调整';

      // 保存到ini配置文件
      IniPropStorage1.IniSection := '参数';
      IniPropStorage1.Active := True;
      IniPropStorage1.WriteInteger('left', g_left);
      IniPropStorage1.WriteInteger('up', g_up);
      IniPropStorage1.WriteInteger('down', g_down);
      IniPropStorage1.WriteInteger('rigth', g_rigth);
      IniPropStorage1.Active := False;

      Exit;
    end;
  end
  else if Length(AData) = 110 then
  begin
    if (CompareText(Copy(S, 1, 10), 'LowVersion') = 0) then
    begin
      // 低版本，需要升级
      Timer_link.Enabled := False;
      S_MESSAGE := '错误，客户端版本太低，请升级。';
      Form_main.ShowMyMsg;
      // 新程序的下载地址
      Form_main.Label_url.Caption := Trim(Copy(S, 11, 110));
      Form_main.Label_rec_tip.Visible := False;
      Form_main.Label_url.Visible := True;

      Exit;
    end;
  end
  else if Length(AData) = 6 then
  begin
    if (CompareText(Copy(S, 1, 4), 'Rece') = 0) then
    begin
      // 收到确认
      Form_setup.AddLog('Rece ' + Copy(S, 5, 2));

      Exit;
    end;
  end;

  // 上面不能解析的错误命令信息，显示一下
  S_MESSAGE := '错误，收到：' + S;
  Form_main.ShowMyMsg;
  Form_setup.AddLog('收到错误命令: ' + S, True); // 加日志
end;

procedure TDM.Timer_debugTimer(Sender: TObject);
var
  S: string;
begin
  // 关闭Debug模式
  Form_setup.TabSheet_debug.TabVisible := False;
  S_MESSAGE := '调试模式关闭';
  Form_main.ShowMyMsg;
  Timer_debug.Enabled := False;
  // 客户端修改了参数，服务器增加待办提醒，（服务器修改的不增加）
  if B_param_enter then
  begin
    B_param_enter := False;
    if (S_next_param <> S_param) then
    begin
      with Form_main do
      begin
        // 转换UTF8，再base64编码，服务器收到后，自动增加待办事项
        S := EncodeStringBase64(AnsiToUtf8(Trim(Edit_name.Text))) +
          ' ' + EncodeStringBase64(AnsiToUtf8(Trim(Edit_dept.Text)));
      end;
      IdUDPClient_setup.Send('TIPChangedParam' + S);
    end;
  end;
end;

procedure TDM.Timer_handTimer(Sender: TObject);
begin
  // 握手定时器，30秒超时，或者服务器要求停止
  Timer_hand.Enabled := False;

  Form_main.Action_end.Execute;
  S_MESSAGE := '错误，与接收服务器握手失败';
  Form_main.ShowMyMsg;

  Form_other.Image1.Picture := nil;      // 不显示预览图片了

  // 开启连接定时器，重新连接服务器
  B_REGISTERED_ERR := True;
  Timer_link.Enabled := True;
end;

procedure TDM.Timer_linkTimer(Sender: TObject);
begin
  // 连接定时器，平常为5秒定时
  Timer_link.Interval := 5000;

  if B_REGISTERED_ERR then
  begin
    B_REGISTERED_ERR := False;
    // ********* 第一步：发送 Registered **********
    // 调用连接服务器过程，申请接入，附带版本号
    // 服务器根据IP从用户表取得信息，
    // 如果没有，回传AskRegistered
    // 如果  有，回传User+用户信息
    IdUDPClient_setup.Send('Registered' + S_version);
    Sleep(100);

    S_MESSAGE := '正在连接服务器，请稍等...';
    Form_main.ShowMyMsg;                //不能在Form_main还没有创建前调用
  end
  else
  begin
    B_REGISTERED_ERR := True;
    S_MESSAGE := '错误，连接服务器失败';
    Form_main.ShowMyMsg;
  end;
end;

function TDM.Open_UDPClient(): boolean;
begin
  Result := True;
  if not IdUDPClient_sent.Active then
  begin
    try
      IdUDPClient_sent.Host := DM.S_REC_SERVER_IP;
      IdUDPClient_sent.Port := DM.I_REC_SERVER_PORT;
      IdUDPClient_sent.BufferSize := DM.I_REC_SERVER_BUFF;
      IdUDPClient_sent.Active := True;
    except
      S_MESSAGE := '接收图像服务器IP参数错误!';
      Form_main.ShowMyself;
      Result := False;
    end;
  end;
end;

function TDM.IsHomology(bmp1, bmp2: Graphics.TbitMap; I: integer = 0): boolean;
type
  Trgb24 = packed record
    b, g, r, a: byte;
  end;
  Trgb24scanline = array [word] of Trgb24;
  pTrgb24scanline = ^Trgb24scanline;
var
  p0, p1: pTrgb24scanline;
  x, y, J: integer;
begin
  // 比较两个图像，差异的像素大于 I ，表示图像不相等
  Result := False;
  if (bmp1.Height <> bmp2.Height) or (bmp1.Width <> bmp2.Width) then exit;
  J := 0;

  for y := 0 to bmp1.Height - 1 do
  begin
    p0 := bmp1.ScanLine[y];
    p1 := bmp2.ScanLine[y];
    for x := 0 to bmp1.Width - 1 do
    begin
      if (p0^[x].r = p1^[x].r) and (p0^[x].g = p1^[x].g) and
        (p0^[x].b = p1^[x].b) then
      begin
        Result := True;
      end
      else
      begin
        if J > I then
        begin
          Result := False;
          Break;
        end
        else
          J := J + 1;
      end;
    end;
    if not Result then Break;
  end;
end;

procedure TDM.IsToday;
var
  Year, Month, Day: word;
  Hour, Min, Sec, MSec: word;
begin
  DecodeDateTime(Now, Year, Month, Day, Hour, Min, Sec, MSec);
  if (W_y <> Year) or (W_m <> Month) or (W_d <> Day) then
  begin
    I_REC_SERVER_NUM := 1;              // 当天的编号清零
  end;
  W_y := Year;
  W_m := Month;
  W_d := Day;
  W_h := Hour;
  W_f := Min;
end;


end.
